﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace DotNetRepository.Tools
{
    public static class ReflectionExtensions
    {
        public static string GetName(this Type type)
        {
            return type == null ? null : type.Name;
        }
        
        public static IEnumerable<FieldInfo> GetFields(this IEnumerable<MemberInfo> memberInfoSet)
        {
            return (from memberInfo in memberInfoSet
                    where memberInfo.MemberType == MemberTypes.Field
                    select memberInfo.DeclaringType.GetField(memberInfo.Name, PublicAndPrivateInstanceFlags)).ToList();
        }

        public static IEnumerable<PropertyInfo> GetProperties(this IEnumerable<MemberInfo> memberInfoSet)
        {
            return (from memberInfo in memberInfoSet
                    where memberInfo.MemberType == MemberTypes.Property
                    select memberInfo.DeclaringType.GetProperty(memberInfo.Name, PublicAndPrivateInstanceFlags)).ToList();
        }

        public static IEnumerable<MemberInfo> MembersInheritingFrom<TBaseType>(this Type reflectedType)
        {
            return (from memberInfo in reflectedType.GetMembers(PublicAndPrivateInstanceFlags)
                    where (memberInfo.MemberType == MemberTypes.Field || memberInfo.MemberType == MemberTypes.Property) &&
                          (memberInfo.GetReturnType().GetInterfaces().Contains(typeof(TBaseType)) ||
                          memberInfo.GetReturnType().IsAssignableFrom(typeof(TBaseType)))
                    select memberInfo).ToList();
        }

        public static IEnumerable<MemberInfo> GetAllInstanceMembers(this Type reflectedType)
        {
            return reflectedType.GetMembers(PublicAndPrivateInstanceFlags);
        }

        public static bool ContainsFieldOrProperty(this Type reflectedType, string name)
        {
            return 
                reflectedType.GetProperty(name, PublicAndPrivateInstanceFlags) != null ||
                reflectedType.GetField(name,PublicAndPrivateInstanceFlags) != null;
        }

        public static object GetValue(this MemberInfo memberInfo, object obj)
        {
            if (!(memberInfo.MemberType == MemberTypes.Field ||
                memberInfo.MemberType == MemberTypes.Property))
                return null;

            if (memberInfo.MemberType == MemberTypes.Field)
                return memberInfo.DeclaringType.GetField(memberInfo.Name, PublicAndPrivateInstanceFlags).GetValue(obj);

            if (memberInfo.MemberType == MemberTypes.Property)
                return memberInfo.DeclaringType.GetProperty(memberInfo.Name, PublicAndPrivateInstanceFlags).GetValue(obj, null);

            return null;
        }

        public static IEnumerable<object> GetValues(this IEnumerable<MemberInfo> memberInfos, object obj)
        {
            return memberInfos.Select(m => m.GetValue(obj));
        }

        public static object GetValue(this Type type, string name, object obj)
        {
            var memberInfo = type.GetMember(name, PublicAndPrivateInstanceFlags).FirstOrDefault();
            return memberInfo.GetValue(obj);
        }

        public static bool SetValue(this MemberInfo memberInfo, object obj, object value)
        {
            if (!(memberInfo.MemberType == MemberTypes.Field ||
                memberInfo.MemberType == MemberTypes.Property))
                return false;

            if (memberInfo.MemberType == MemberTypes.Field)
            {
                memberInfo.DeclaringType.GetField(memberInfo.Name, PublicAndPrivateInstanceFlags).SetValue(obj, value);
                return true;
            }

            if (memberInfo.MemberType == MemberTypes.Property)
            {
                memberInfo.DeclaringType.GetProperty(memberInfo.Name, PublicAndPrivateInstanceFlags).SetValue(obj, value,null);
                return true;
            }
                
            return false;
        }

        public static bool SetValue(this Type type, string name, object obj, object value )
        {
            var memberInfo = type.GetMember(name, PublicAndPrivateInstanceFlags).FirstOrDefault();
            return memberInfo.SetValue(obj, value);
        }

        public static Type GetReturnType(this MemberInfo memberInfo)
        {
            if (!(memberInfo.MemberType == MemberTypes.Field ||
                memberInfo.MemberType == MemberTypes.Property ||
                memberInfo.MemberType == MemberTypes.Method))
                return null;

            if (memberInfo.MemberType == MemberTypes.Field)
                return memberInfo.DeclaringType.GetField(memberInfo.Name, PublicAndPrivateInstanceFlags).FieldType;

            if (memberInfo.MemberType == MemberTypes.Property)
                return memberInfo.DeclaringType.GetProperty(memberInfo.Name, PublicAndPrivateInstanceFlags).PropertyType;

            if (memberInfo.MemberType == MemberTypes.Method)
                return memberInfo.DeclaringType.GetMethod(memberInfo.Name, PublicAndPrivateInstanceFlags).ReturnType;

            return null;
        }

        private const BindingFlags PublicAndPrivateInstanceFlags = BindingFlags.NonPublic | BindingFlags.Public |
                                                                   BindingFlags.Instance;

    }
}
